#include "include/lib.h"
#include "include/cxxstd.h"
#include "memory/memory.h"
#include "boot/boot.h"
#include "cxx/cxx.h"
#include "cxx.test.h"

using namespace zero;

namespace zero
{
extern std::STDHANDLE exceptionHandle;
}

namespace zerotest
{
static TestResult trBase;

// class and exception implementation <([{
base::base(TestResult* r) : tr(r)
{
	b = 1;
	tr->newOrder <<= 4; tr->newOrder += 1;
}

base::~base(void)
{
	tr->deleteOrder <<= 4; tr->deleteOrder += 1;
}

EXPORT_CLASS_TYPEINFO(_ZTVN8zerotest4baseE, _ZTIN8zerotest4baseE);

inherit::inherit(TestResult* r) : base(r)
{
	i = 2;
	tr->newOrder <<= 4; tr->newOrder += 2;
}

inherit::~inherit(void)
{
	tr->deleteOrder <<= 4; tr->deleteOrder += 2;
}
EXPORT_CXX_TEXT(_ZN8zerotest7inheritD0Ev);
EXPORT_CXX_TEXT(_ZN8zerotest7inheritD1Ev);
EXPORT_CXX_TEXT(_ZN8zerotest7inheritD2Ev);

void inherit::voo(void)
{
	tr->vcall <<= 4; tr->vcall += 2;
}
EXPORT_CXX_TEXT(_ZN8zerotest7inherit3vooEv);
EXPORT_CXX_TEXT(_ZTv0_n12_N8zerotest7inherit3vooEv);

void inherit::m(void)
{
}
EXPORT_CXX_TEXT(_ZN8zerotest7inherit1mEv);
EXPORT_CXX_TEXT(_ZTv0_n20_N8zerotest7inherit1mEv);

void inherit::n(void)
{
}
EXPORT_CXX_TEXT(_ZN8zerotest7inherit1nEv);
EXPORT_CXX_TEXT(_ZTv0_n20_N8zerotest7inherit1nEv);

void inherit::mn(void)
{
}
EXPORT_CXX_TEXT(_ZN8zerotest7inherit2mnEv);
EXPORT_CXX_TEXT(_ZTv0_n32_N8zerotest7inherit2mnEv);

EXPORT_CLASS_TYPEINFO(_ZTVN8zerotest7inheritE, _ZTIN8zerotest7inheritE);

grandson::grandson(TestResult* r) : inherit(r)
{
	g = 3;
	tr->newOrder <<= 4; tr->newOrder += 3;
}
EXPORT_CXX_TEXT(_ZN8zerotest8grandsonC2EPNS_10TestResultE);

grandson::~grandson(void)
{
	tr->deleteOrder <<= 4; tr->deleteOrder += 3;
}
EXPORT_CXX_TEXT(_ZN8zerotest8grandsonD0Ev);
EXPORT_CXX_TEXT(_ZN8zerotest8grandsonD1Ev);
EXPORT_CXX_TEXT(_ZN8zerotest8grandsonD2Ev);

void grandson::voo(void)
{
	tr->vcall <<= 4; tr->vcall += 3;
}
EXPORT_CXX_TEXT(_ZN8zerotest8grandson3vooEv);
EXPORT_CXX_TEXT(_ZTv0_n12_N8zerotest8grandson3vooEv);

EXPORT_CLASS_TYPEINFO(_ZTVN8zerotest8grandsonE, _ZTIN8zerotest8grandsonE);

test_exception::test_exception(TestResult* r) throw() : tr(r)
{
	tr->ex <<= 4; tr->ex += 7;
}

test_exception::~test_exception(void) throw()
{
	tr->ex <<= 4; tr->ex += 8;
}
EXPORT_CXX_TEXT(_ZN8zerotest14test_exceptionD1Ev);
EXPORT_CXX_TEXT(_ZN8zerotest14test_exceptionD2Ev);

const char* test_exception::what(void) const throw()
{
	return "zerotest::test_exception";
}

EXPORT_CLASS_TYPEINFO(_ZTVN8zerotest14test_exceptionE, _ZTIN8zerotest14test_exceptionE);

// }])>

// testcases <([{
void new_delete(void)
{
	trBase.newOrder = 0;
	trBase.deleteOrder = 0;
	grandson* gs = new grandson(&trBase);
	assert(trBase.newOrder == 0x123);
	assert(sizeof(grandson) == 28);
	// mbl abbreviates from memory binary layout.
	unsigned int* mbl = reinterpret_cast<unsigned int*>(gs);
	unsigned int vtable = reinterpret_cast<unsigned int>(&_ZTVN8zerotest8grandsonE);
	assert(mbl[0] == vtable + 24);
	assert(mbl[1] == reinterpret_cast<unsigned int>(&trBase));
	assert(mbl[2] == 1);
	assert(mbl[3] == 2);
	assert(mbl[4] == 3);
	assert(mbl[5] == vtable + 80);
	assert(mbl[6] == vtable + 112);
	delete gs;
	assert(trBase.deleteOrder == 0x321);
}

void typeinfo(void)
{
	grandson* gs = new grandson(&trBase);
	unsigned int gsoffset = reinterpret_cast<unsigned int>(gs);
	void* gsoffset0 = reinterpret_cast<void*>(gsoffset);
	void* gsoffset20 = reinterpret_cast<void*>(gsoffset + 20);
	void* gsoffset24 = reinterpret_cast<void*>(gsoffset + 24);
	assert(gsoffset0 == (base*) gs);
	assert(gsoffset0 == (inherit*) gs);
	assert(gsoffset20 == (iface*) gs);
	assert(gsoffset20 == (ifacem*) gs);
	assert(gsoffset20 == (ifacemn*) gs);
	assert(gsoffset24 == (ifacen*) gs);
	base* b = gs;
	ifacem* m = gs;
	assert(gsoffset0 == dynamic_cast<inherit*>(b));
	assert(gsoffset20 == dynamic_cast<ifacem*>(b));
	assert(gsoffset20 == dynamic_cast<iface*>(b)); // ambiguous cast, note it's virtual base.
	assert(gsoffset24 == dynamic_cast<ifacen*>(m)); // cross cast.
	assert(NULL == dynamic_cast<ifacexx*>(b));
	delete gs;
}

void vcall(void)
{
	trBase.deleteOrder = 0;
	trBase.vcall = 0;
	grandson* gs = new grandson(&trBase);
	inherit* i = gs;
	i->voo();
	assert(trBase.vcall == 0x3);
	base* b = gs;
	delete b;
	assert(trBase.deleteOrder == 0x321);
}

void ex5(void) throw (test_exception)
{
	throw test_exception(&trBase);
}

void ex4(void) throw ()
{
	throw test_exception(&trBase);
}

void ex3(void)
{
	try {
		throw test_exception(&trBase);
		trBase.ex <<= 4; trBase.ex += 1;
	} catch (std::bad_exception& ex) {
		trBase.ex <<= 4; trBase.ex += 6;
	} catch (test_exception& ex) {
		try {
			trBase.ex <<= 4; trBase.ex += 2;
			throw;
		} catch (std::exception& ex) {
			trBase.ex <<= 4; trBase.ex += 3;
			throw;
		}
	}
}

void ex2(void)
{
	ex3();
	trBase.ex <<= 4; trBase.ex += 4;
}

void ex1(void)
{
	try {
		ex2();
	} catch (...) {
		trBase.ex <<= 4; trBase.ex += 5;
	}
}

static struct {
	unsigned int eip;
	unsigned int ebp;
	unsigned int esp;
} jmpTray;

void ex_testcase_handle(void*)
{
	trBase.ex <<= 4; trBase.ex += 9;
	// mini longjmp.
	asm volatile (
			"movl %1, %%ebp\n\t" \
			"movl %2, %%esp\n\t" \
			"jmp *%0\n\t" \
			: \
			: "r" (jmpTray.eip), \
			"m" (jmpTray.ebp), \
			"m" (jmpTray.esp) \
			: );
}

void exception(void)
{
	trBase.ex = 0;
	ex1();
	assert(trBase.ex == 0x72358); // 7 and 8 is class test_exception construction/destruction.
	trBase.ex = 0;
	// Expected stack layout when calling ex4 is ex4 >> __cxa_call_unexpected >> ex_testcase_handle. We need a mini version setjmp/longjmp to return here quickly.
	asm volatile (
			"movl %3, %0\n\t" \
			"movl %%ebp, %1\n\t" \
			"movl %%esp, %2\n\t" \
			: "=m" (jmpTray.eip), \
			"=m" (jmpTray.ebp), \
			"=m" (jmpTray.esp) \
			: "q" (&&jmp_address) \
			: );
	ex4();
jmp_address:
	try {
		ex5();
	} catch (...) {
		trBase.ex <<= 4; trBase.ex += 0xa;
	}
	assert(trBase.ex == 0x797a8); // Note: current implementation doesn't include free exception in __cxa_call_unexpected.
}
// }])>

void cxx_test_entry(laddr_t fixAddr, laddr_t picAddr)
{
	new_delete();
	typeinfo();
	vcall();
	zero::exceptionHandle = ex_testcase_handle;
	exception();

	typedef void (*FOO)();
	const char* exstr;
#define _C(c) c, sizeof(c)

	relocateELF(fixAddr, false);
	ABIBase::push(ABIBase::create(reinterpret_cast<unsigned char*>(FIX_ELF_INITIAL_ADDRESS), os_allocator, os_free));
	try {
		FOO func = reinterpret_cast<FOO>(ABIBase::top()->codeEntry);
		ABIBase::top()->load();
		func();
		ABIBase::top()->unload();
	} catch (std::exception& e) {
		exstr = e.what();
		assert(bstrcmp(_C("fix:zerotest::exception"), exstr));
	}
	ABIBase::pop();

	relocateELF(picAddr, true);
	ABIBase::push(ABIBase::create(reinterpret_cast<unsigned char*>(PIC_ELF_INITIAL_ADDRESS), os_allocator, os_free));
	try {
		FOO func = reinterpret_cast<FOO>(ABIBase::top()->codeEntry);
		ABIBase::top()->load();
		func();
		ABIBase::top()->unload();
	} catch (std::exception& e) {
		exstr = e.what();
		assert(bstrcmp(_C("pic:zerotest::exception"), exstr));
	}
	ABIBase::pop();
}
}

// vim: foldmarker=<([{,}])> foldmethod=marker
